<?php
namespace lib\JhWechat;

/**
 * 加解密
 * @Auth: JH <hu@lunaz.cn>
 * Class Crypt
 * @package lib\JhWechat
 */
class Crypt
{

    private $key;
    private $config;

    function __construct( $config )
    {
        if ( !isset($config['encodingAesKey']) || strlen( $config['encodingAesKey'] ) !== 43 ) {
            throw new Exception( '不合法的AESKey' );
        }
        $this->key = base64_decode( $config['encodingAesKey'] . '=', true );
        $this->config = $config;
    }

    /**
     *解密
     * @param string $string 需要解密的密文
     * @return string 返回解密明文
     * @throws Exception
     */
    function deCrypt( $string )
    {
        try {
            $text = base64_decode( $string, true );
            $module = mcrypt_module_open( MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '' );
            mcrypt_generic_init( $module,  $this->key, substr( $this->key, 0, 16 ) );
            $text = mdecrypt_generic( $module, $text );
            mcrypt_generic_deinit( $module );
            mcrypt_module_close( $module );
        } catch ( Exception $e ) {
            throw new Exception( 'AES解密失败' );
        }

        try {
            //去除补位字符
            $result = self::decode( $text );

            //去除16位随机字符串,网络字节序和AppId
            if ( strlen( $result ) < 16 ) {
                return '';
            }
            $content = substr( $result, 16, strlen( $result ) );
            $listLen = unpack( 'N', substr( $content, 0, 4 ) );
            $xml = substr( $content, 4, $listLen[ 1 ] );
            $fromAppId = trim( substr( $content, $listLen[ 1 ] + 4 ) );
        } catch ( Exception $e ) {
            throw new Exception( '公众平台发送的xml不合法' );
        }
        if ( $this->config['appId'] != $fromAppId ) {
            throw new Exception( '校验AppID不一致' );
        }
        return $xml;
    }

    /**
     * 对明文进行加密.
     *
     * @param string $text 需要加密的明文
     * @return string 加密后的密文
     * @throws Exception
     */
    function enCrypt( $text )
    {
        try {
            //获得16位随机字符串，填充到明文之前
            $text = self::randStr() . pack( 'N', strlen( $text ) ) . $text . $this->config['appId'];
            $module = mcrypt_module_open( MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '' );
            //$key = base64_decode( $this->key . '=', true );
            //使用自定义的填充方式对明文进行补位填充
            $text = self::encode( $text );
            mcrypt_generic_init( $module, $this->key, substr( $this->key, 0, 16 ) );
            //加密
            $encrypted = mcrypt_generic( $module, $text );
            mcrypt_generic_deinit( $module );
            mcrypt_module_close( $module );

            //使用BASE64对加密后的字符串进行编码
            return base64_encode( $encrypted );
        } catch ( Exception $e ) {
            throw new Exception( 'AES加密失败' );
        }
    }

    /**
     * 对解密后的明文进行补位删除.
     *
     * @param string $text 解密后的明文
     *
     * @return string 删除填充补位后的明文
     */
    private static function decode( $text )
    {
        $pad = ord( substr( $text, -1 ) );

        if ( $pad < 1 || $pad > 32 ) {
            $pad = 0;
        }

        return substr( $text, 0, ( strlen( $text ) - $pad ) );
    }

    /**
     * 对需要加密的明文进行填充补位
     * @param string $text 需要进行填充补位操作的明文
     * @return string 补齐明文字符串
     */
    private static function encode( $text )
    {
        //计算需要填充的位数
        $pad = 32 - ( strlen( $text ) % 32 );
        $pad = $pad !== 0 ? $pad : 32;
        //获得补位所用的字符
        $chr = chr( $pad );
        $tmp = '';
        for ( $i = 0 ; $i < $pad ; $i++ ) {
            $tmp .= $chr;
        }
        return $text . $tmp;
    }

    /**
     * 随机生成16位字符串.
     * @return string 生成的字符串
     */
    static function randStr()
    {
        return substr( str_shuffle( '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' ), 0, 16 );
    }


}
